package com.fand.generator.template;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.springframework.beans.BeanUtils;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.CollectionUtils;

import com.fand.generator.mapper.BusinessMapper;
import com.fand.generator.mapper.EntityMapper;
import com.fand.generator.mapper.ModuleMapper;
import com.fand.generator.mapper.PoMapper;
import com.fand.generator.mapper.ProjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import freemarker.ext.beans.BeansWrapper;
import freemarker.template.Configuration;

/**
 * @author yaoHT
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class RunSSSTemplate implements RunTemplate {
	private Configuration configuration;

	private final Map<String, String> contextMap = Maps.newHashMap();

	private String extendName = "Oracle";

	private final Map<String, Object> model = Maps.newHashMap();

	private String outputDir;

	private boolean overrideFile = true;

	private Map<String, PoMapper> poMapperMap;

	private ProjectMapper projectMapper;

	private final List<String> serialVersionUIDList = Lists.newArrayList();

	public void init() {
		Validate.notNull(this.projectMapper);

		Validate.notNull(this.outputDir);
	}

	private void makeFile(final String templateString, final String templateName) throws Exception {
		RunSSSTemplate.log.info(templateString);

		RunSSSTemplate.log.info(templateName);

		final String filePathName = com.fand.template.utils.TemplateUtils.processString(templateString,
				this.contextMap);

		RunSSSTemplate.log.info(filePathName);

		final File file = new File(filePathName);

		if (file.exists()) {
			RunSSSTemplate.log.info("file.exists():true");

			if (this.overrideFile) {
				FileUtils.deleteQuietly(file);
			} else {
				return;
			}
		}

		final freemarker.template.Template template = this.configuration.getTemplate(templateName);

		this.modelPutSerialVersionUID();

		final String data = FreeMarkerTemplateUtils.processTemplateIntoString(template, this.model);

		FileUtils.writeStringToFile(file, data);
	}

	private void markController(final String businessCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}controller${separator!}${filename!}Controller.java";

		this.makeFile(javaTemplateString, "Controller.ftl");
	}

	private void markDao(final String businessCode) throws Exception {
		String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}dao${separator!}${filename!}Dao.java";

		this.makeFile(javaTemplateString, "Dao.java.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}dao${separator!}${filename!}JdbcDao.java";

		this.makeFile(javaTemplateString, "JdbcDao.java.ftl");
	}

	private void markDto(final String entityCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}dto${separator!}${filename!}Dto.java";

		this.makeFile(javaTemplateString, "Dto.java.ftl");
	}

	private void markPo(final String entityCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}po${separator!}${filename!}.java";

		this.makeFile(javaTemplateString, "Po.java.ftl");
	}

	private void markService(final String businessCode) throws Exception {
		String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}service${separator!}${filename!}Service.java";

		this.makeFile(javaTemplateString, "Service.java.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}service${separator!}${filename!}DefaultService.java";

		this.makeFile(javaTemplateString, "DefaultService.java.ftl");
	}

	private void markSpringConfig(final String businessCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}${moduleMapperKey!}${separator!}applicationContext-${filename!}.xml";

		this.makeFile(javaTemplateString, "applicationContext-service.ftl");
	}

	private void markSQL(final String entityCode) throws Exception {
		String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${extendName?uncap_first}${separator!}${moduleMapperKey!}${separator!}${entityCode?uncap_first}${separator!}update${filename!}By1.ftl";

		this.makeFile(javaTemplateString, "updateBy1.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${extendName?uncap_first}${separator!}${moduleMapperKey!}${separator!}${entityCode?uncap_first}${separator!}select${filename!}By1ForMappedObjectList.ftl";

		this.makeFile(javaTemplateString, "selectBy1ForMappedObjectList.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${extendName?uncap_first}${separator!}${moduleMapperKey!}${separator!}${entityCode?uncap_first}${separator!}select${filename!}By1ForObject.ftl";

		this.makeFile(javaTemplateString, "selectBy1ForObject.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${extendName?uncap_first}${separator!}${moduleMapperKey!}${separator!}${entityCode?uncap_first}${separator!}where${filename!}By1.ftl";

		this.makeFile(javaTemplateString, "whereBy1.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${extendName?uncap_first}${separator!}${moduleMapperKey!}${separator!}${entityCode?uncap_first}${separator!}insert${filename!}By1.ftl";

		this.makeFile(javaTemplateString, "insertBy1.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${extendName?uncap_first}${separator!}${moduleMapperKey!}${separator!}${entityCode?uncap_first}${separator!}delete${filename!}By1.ftl";

		this.makeFile(javaTemplateString, "deleteBy1.ftl");

		javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}resources${separator!}sql${separator!}${extendName?uncap_first}${separator!}${moduleMapperKey!}${separator!}${entityCode?uncap_first}${separator!}delete${filename!}ByIds.ftl";

		this.makeFile(javaTemplateString, "deleteByIds.ftl");
	}

	private void markValidator(final String entityCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}validator${separator!}${filename!}Validator.java";

		this.makeFile(javaTemplateString, "Validator.java.ftl");
	}

	private void markVo(final String businessCode) throws Exception {
		final String javaTemplateString = "${outputDir!}${separator!}src${separator!}main${separator!}java${separator!}${packageDir!}${separator!}${moduleMapperKey!}${separator!}vo${separator!}${filename!}Vo.java";

		this.makeFile(javaTemplateString, "Vo.java.ftl");
	}

	private void modelPutSerialVersionUID() {
		String serialVersionUID = RandomStringUtils.randomNumeric(18);

		while (StringUtils.startsWithAny(serialVersionUID, new String[] { "0", "9" })
				|| this.serialVersionUIDList.contains(serialVersionUID)) {
			serialVersionUID = RandomStringUtils.randomNumeric(18);
		}

		this.serialVersionUIDList.add(serialVersionUID);

		this.model.put("serialVersionUID", serialVersionUID);
	}

	@Override
	public void run(final Map<String, PoMapper> poMapperMap) throws Exception {
		if (CollectionUtils.isEmpty(this.projectMapper.getModuleMapperMap())) {
			return;
		}

		this.poMapperMap = poMapperMap;

		final String packageDir = StringUtils.replace(this.projectMapper.getPackageCode(), ".", File.separator);

		this.contextMap.put("outputDir", this.outputDir);

		this.contextMap.put("separator", File.separator);

		this.contextMap.put("packageDir", packageDir);

		this.model.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());

		for (final Entry<String, ModuleMapper> moduleMapperEntry : this.projectMapper.getModuleMapperMap()
				.entrySet()) {
			moduleMapperEntry.getValue().setCode(moduleMapperEntry.getKey());

			for (final Entry<String, BusinessMapper> businessMapperEntry : moduleMapperEntry.getValue()
					.getBusinessMap().entrySet()) {
				businessMapperEntry.getValue().setCode(businessMapperEntry.getKey());
			}
		}

		this.model.put("projectMapper", this.projectMapper);

		this.model.put("packageCode", this.projectMapper.getPackageCode());

		this.model.put("extendName", this.extendName);

		this.contextMap.put("extendName", this.extendName);

		for (final Entry<String, ModuleMapper> moduleMapperEntry : this.projectMapper.getModuleMapperMap()
				.entrySet()) {
			moduleMapperEntry.getValue().setCode(moduleMapperEntry.getKey());

			this.contextMap.put("moduleMapperKey", moduleMapperEntry.getKey());

			this.model.put("moduleMapperKey", moduleMapperEntry.getKey());

			this.model.put("moduleMapper", moduleMapperEntry.getValue());

			if (CollectionUtils.isEmpty(moduleMapperEntry.getValue().getBusinessMap())) {
				continue;
			}

			for (final Entry<String, BusinessMapper> businessMapperEntry : moduleMapperEntry.getValue()
					.getBusinessMap().entrySet()) {
				if (CollectionUtils.isEmpty(businessMapperEntry.getValue().getEntityMapperList())) {
					businessMapperEntry.getValue().setEntityMapperList(new ArrayList<EntityMapper>());
				}

				for (final EntityMapper entityMapper : businessMapperEntry.getValue().getEntityMapperList()) {
					if (!this.poMapperMap.containsKey(entityMapper.getCode())) {
						continue;
					}

					final PoMapper poMapper = this.poMapperMap.get(entityMapper.getCode());
					
					BeanUtils.copyProperties(poMapper, entityMapper);

					this.model.put("poMapper", entityMapper);

					this.contextMap.put("filename", entityMapper.getCode());

					this.contextMap.put("entityCode", entityMapper.getCode());

					this.model.put("filename", entityMapper.getCode());

					this.model.put("entityCode", entityMapper.getCode());

					this.markPo(entityMapper.getCode());

					this.markDto(entityMapper.getCode());

					if (entityMapper.isMarkService() && entityMapper.isMarkController()) {
						this.markValidator(entityMapper.getCode());
					}

					this.markSQL(entityMapper.getCode());
				}

				this.setBusiness(businessMapperEntry.getValue());

				this.markVo(businessMapperEntry.getKey());

				this.markDao(businessMapperEntry.getKey());

				this.markService(businessMapperEntry.getKey());

				this.markSpringConfig(businessMapperEntry.getKey());

				this.markController(businessMapperEntry.getKey());
			}

			this.model.put("projectMapper", this.projectMapper);
		}
	}

	private void setBusiness(final BusinessMapper businessMapper) {
		this.contextMap.put("filename", businessMapper.getCode());

		this.contextMap.put("businessCode", businessMapper.getCode());

		this.model.put("businessMapper", businessMapper);

		this.model.put("businessCode", businessMapper.getCode());

		if (CollectionUtils.isEmpty(businessMapper.getEntityMapperList())) {
			businessMapper.setEntityMapperList(new ArrayList<EntityMapper>());
		}

		for (final EntityMapper entityMapper : businessMapper.getEntityMapperList()) {
			if (!this.poMapperMap.containsKey(entityMapper.getCode())) {
				continue;
			}

			final PoMapper poMapper = this.poMapperMap.get(entityMapper.getCode());

			BeanUtils.copyProperties(poMapper, entityMapper);
		}

		this.model.put("poMapperList", businessMapper.getEntityMapperList());
	}
}
